package com.example.f1.controller.tcc;

import com.example.f1.domain_param.CartParam;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
import org.example.dayu521.OrRes;

//对资源的竞争类似于if语句的判断,一个执行只有通过if判断,才能到达它的目的
// 因为在if之后就不能回退,所以状态是确定的

//
@LocalTCC
public interface CartService {

    //设置TwoPhaseBusinessAction的 useTCCFence属性为true
    // 来解决当前接口的tcc问题,即tcc的幂等,悬挂,空回滚问题
    //注意,需要在分支事务的数据库建新的表,具体见连接
    // https://github.com/seata/seata/pull/3545/files
    // 博客参考
    // https://seata.io/zh-cn/blog/seata-tcc-fence.html
    // https://seata.io/zh-cn/blog/seata-tcc.html

    //tcc本身能不能经过修改,在发起回滚时,事先询问服务是否得到try了,再进行回滚?
    //但这只能是在TM认为try超时时才可行

    //而产生空回滚问题原因有一种情况,
    //首先我们调用远程服务时,是同步调用的.即先调用一个,成功后,获取结果,做一些状态变化操作,再调用下一个
        //我们不采用异步方式,同时调用多个服务后,等待最终结果.如果异步调用,成功的情况下,则会提前注册分支事务
        //      那么在一个服务try失败的情况下,所有服务都收到rollback,那些还没try的服务,则会空回滚
    //这里,我们不考虑异步方式.
    // 那么如果调用超时(出错的情况,则就不是空回滚),则TM就会终止事务.于是很多服务会收到rollback
    // 但哪些服务会形成空回滚呢?
    // 因为是同步调用,所有只有最后一个服务才可能会产生空回滚

    // 那么怎么产生空回滚?
    // 1 在调用返回前,TM无法做出,调用已经到达分支事务,的决定,
    // 但这时候一些不可控因素(这里基本上指的是超时了)发生了,导致全局事务无法继续,TM不得不决定回滚事务,
    // 那么,try在rollback之前,这个顺序就无法得到保证了,才产生空回滚
    // 2 在TM崩溃时,由TC做出事务失败回滚的决定.
    // 这两种情况本质上是一致的,都可以认为是由TM造成的(1.是网络超时直接原因,2.是崩溃原因)

    //try commit rollback
    // try和commit的先后顺序关系是保持的
    // commit与rollback的互斥关系也是保持的(他们之间没有关系)
    // 然而,我们只是期待,正常情况下try和rollback是保持先后关系
    // 在空回滚发生时,在rollback函数中,检测到空回滚(try方不知道是否还存在),顺序就不成立,
    // 同时可以转化为竞争关系.(注意,这个检测再转换通常是原子的,要么通过加锁保证原子性)
    // 所以严格意义上,顺序关系和竞争关系都可能成立.这种情况下,两者都不要执行是个好选择.

    //进而产生悬挂问题,要在这个空回滚问题之后了
    // 这里的一个事实是,一些try调用,在特定时间后,还会到达服务方B,最终导致悬挂的try

    // 我们检测到空回滚后直接记录错误日志,同时设置状态,再防止悬挂执行

    //所以,真正关注的应该是commit与rollback的幂等性(try阶段,我们禁止重试)
    //回滚时还需要判断,是当前分支事务产生的回滚还是其他事务产生的回滚
    // 或者是TM的原因导致回滚,这种情况下通常会出现悬挂,有必要保证try和rollback不能干扰执行,
    // todo 还有问题

    //@BusinessActionContextParameter注解把参数放到BusinessActionContext中,
    // 这样,在二阶段可以直接声明方法参数BusinessActionContext,然后从这里得到一阶段的参数
    //源码
    // 具体逻辑放到TccActionInterceptor的invoke方法中,直到ActionInterceptorHandler.java的getOrCreateActionContextAndResetToArguments方法中
    @TwoPhaseBusinessAction(name = "cart_service_rob",
            commitMethod = "rob_confirm",
            rollbackMethod = "rob_cancel",
            commitArgsClasses = CartParam.Rob.class,
            rollbackArgsClasses = CartParam.Rob.class)
     OrRes<?> rob( @BusinessActionContextParameter(paramName = "params") CartParam.Rob rob);

    //返回值可以是TwoPhaseResult

    // 1.5.1之后,我们可以直接获取rob参数,不需要再声明BusinessActionContext,然后从它间接得到参数,具体是
    //      参数获取时需要添加@BusinessActionContextParameter注解,且在rob函数声明时也需要@BusinessActionContextParameter注解
    //      然后,这些参数类型提前在@TwoPhaseBusinessAction注解中配置
    // https://github.com/seata/seata/pull/3823
    // 所以现在,简而言之,三个方法参数签名是一样的
    boolean rob_confirm(@BusinessActionContextParameter(paramName = "params") CartParam.Rob rob);

    boolean rob_cancel( @BusinessActionContextParameter(paramName = "params") CartParam.Rob rob);
}
